﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Imaging;
using Ultima;
using SpellCaster3.Forms;
using SpellCaster3.Core;
using System.Xml.Serialization;
using System.IO;
using Expand;
using Expand.Linq;
using Expand.Patterns.Command;
using Commands = SpellCaster3.Commands;
using System.Runtime.InteropServices;

namespace SpellCaster3
{
    public partial class HomeForm : Form
    {
        private volatile bool _IconsVisibility = true;
        private EventHandler RecentProfileClickHandler;
        private bool _ChangingSelectedButtonMacro;
        private FormWindowState _BeforeHideWindowState;
        private FormWindowState _LastWindowState;

        private SortedSet<IntPtr> _FormsHandle;
        internal Dictionary<ButtonMacro, IconForm> Icons = new Dictionary<ButtonMacro, IconForm>(50);
        private History _History = new History();
        public History History { get { return _History; } }

        public EventHandler IconFormMoveBeginHandler { get; private set; }
        public EventHandler<MoveEventArgs> IconFormMoveEndHandler { get; set; }

        public bool ShouldSave { get { return salvaToolStripButton.Enabled; } }

        private class IntPtrComparer : IComparer<IntPtr>
        {
            public int Compare(IntPtr x, IntPtr y)
            {
                return x.ToInt64().CompareTo(y.ToInt64());
            }
        }

        public HomeForm()
        {
            InitializeComponent();
            RecentProfileClickHandler = new EventHandler(RecentProfileClick);
            bool loadPathNull = string.IsNullOrWhiteSpace(SpellCaster3.Properties.Settings.Default.LastLoadPath);
            bool savePathNull = string.IsNullOrWhiteSpace(SpellCaster3.Properties.Settings.Default.LastSavePath);
            if (loadPathNull)
                SpellCaster3.Properties.Settings.Default.LastLoadPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
            if (savePathNull)
                SpellCaster3.Properties.Settings.Default.LastSavePath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);

            if (loadPathNull || savePathNull)
                SpellCaster3.Properties.Settings.Default.Save();

            openFileDialog.InitialDirectory = SpellCaster3.Properties.Settings.Default.LastLoadPath;
            saveFileDialog.InitialDirectory = SpellCaster3.Properties.Settings.Default.LastSavePath;

            _FormsHandle = new SortedSet<IntPtr>(new IntPtrComparer());
            _LastWindowState = WindowState;

            WindowStateChanged += new EventHandler<LastValueEventArgs<FormWindowState>>(HomeForm_WindowStateChanged);

            notifyIcon.Icon = SpellCaster3.Properties.Resources.spellcasterIcon;
            Icon = SpellCaster3.Properties.Resources.spellcasterIcon;

            saveFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
            openFileDialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);

            IconFormMoveBeginHandler = new EventHandler(iconForms_MoveBegin);
            IconFormMoveEndHandler = new EventHandler<MoveEventArgs>(iconForms_MoveEnd);

            hueButton.ValueChanged += new EventHandler(hueButton_ValueChanged);

            History.CommandDone += new EventHandler<HistoryEventArgs>(UpdateUndoRedoButtons);
            History.CommandUndone += new EventHandler<HistoryEventArgs>(UpdateUndoRedoButtons);
            History.CommandRedone += new EventHandler<HistoryEventArgs>(UpdateUndoRedoButtons);
            SpellCasterManager.RecentProfilesChanged += new EventHandler(SpellCasterManager_RecentProfilesChanged);

            LoadProfile(SpellCasterManager.CurrentProfile);
            SpellCasterManager_RecentProfilesChanged(null, EventArgs.Empty);
            iconsVisibilityWorker.RunWorkerAsync();
        }

        private void SpellCasterManager_RecentProfilesChanged(object sender, EventArgs e)
        {
            fileRecentiToolStripMenuItem.Enabled = SpellCaster3.Properties.Settings.Default.RecentProfiles != null && SpellCaster3.Properties.Settings.Default.RecentProfiles.Count > 0;
            ToolStripItemCollection items = fileRecentiToolStripMenuItem.DropDownItems;
            fileRecentiToolStripMenuItem.DropDownItems.Clear();
            foreach (Control control in items)
                if (control != null && !control.IsDisposed)
                    control.Dispose();

            if (SpellCaster3.Properties.Settings.Default.RecentProfiles != null)
            {
                int i = 1;
                foreach (string s in SpellCaster3.Properties.Settings.Default.RecentProfiles)
                {
                    ToolStripMenuItem tmp = new ToolStripMenuItem(i.ToString() + " " + Path.GetPathRoot(s) + "..." + Path.DirectorySeparatorChar + Path.GetFileName(Path.GetDirectoryName(s)) + Path.DirectorySeparatorChar + Path.GetFileName(s));
                    fileRecentiToolStripMenuItem.DropDownItems.Add(tmp);
                    tmp.Click += RecentProfileClickHandler;
                    ++i;
                }
            }
        }

        private void HideAllIcons()
        {
            SetAllIconsVisibility(false);
        }

        private void ShowAllIcons()
        {
            SetAllIconsVisibility(true);
        }

        private void SetAllIconsVisibility(bool visible)
        {
            if (DesignMode || !SpellCaster3.Properties.Settings.Default.HideIconsWhenFocusLost) return;
            bool visibility = visible;
            try
            {
                if (InvokeRequired)
                {
                    Invoke(new Action(() =>
                        {
                            foreach (Control control in Icons.Values)
                                if (control.InvokeRequired)
                                    control.Invoke(new Action<bool>((bool arg) => { if (!control.IsDisposed) control.Visible = arg; }), visibility);
                                else
                                    control.Visible = visibility;
                        }));
                }
                else
                {
                    foreach (Control control in Icons.Values)
                        if (control.InvokeRequired)
                            control.Invoke(new Action<bool>((bool arg) => { if (!control.IsDisposed) control.Visible = arg; }), visibility);
                        else
                            control.Visible = visibility;
                }
            }
            catch (ObjectDisposedException) { }
            _IconsVisibility = visibility;
        }

        private void LoadProfile(Profile profile)
        {
            History.Clear();
            SpellCasterManager.UndoSaveCommandsAmount = 0;
            iconsListBox.ClearSelected();
            iconsListBox.DataSource = profile.ButtonsMacro;
            profile.ButtonsMacro.ListChanging += new Expand.ComponentModel.ListChangingEventHandler(ButtonsMacro_ListChanging);
            profile.ButtonsMacro.ListChanged += new ListChangedEventHandler(ButtonsMacro_ListChanged);

            #region Clear bindings
            if (gumpButton.DataBindings.Count > 0)
            {
                //hotkeyControl.DataBindings.RemoveAt(0);
                gumpButton.DataBindings.RemoveAt(0);
                hueButton.DataBindings.RemoveAt(0);
                nameTextBox.DataBindings.RemoveAt(0);
                aliasTextBox.DataBindings.RemoveAt(0);
                keyNameTextBox.DataBindings.RemoveAt(0);
                command1TextBox.DataBindings.RemoveAt(0);
                command2TextBox.DataBindings.RemoveAt(0);
                command3TextBox.DataBindings.RemoveAt(0);
                descriptionTextBox.DataBindings.RemoveAt(0);
            }
            #endregion

            #region Reset fields
            nameTextBox.ResetText();
            aliasTextBox.ResetText();
            keyNameTextBox.ResetText();
            command1TextBox.ResetText();
            command2TextBox.ResetText();
            command3TextBox.ResetText();
            hueButton.Reset();
            gumpButton.Reset();
            _ChangingSelectedButtonMacro = true;
            hotkeyControl.Reset();
            descriptionTextBox.ResetText();
            #endregion

            #region Bindings
            gumpButton.DataBindings.Add("Value", profile.ButtonsMacro, "GumpIndex", false, DataSourceUpdateMode.OnPropertyChanged);
            hueButton.DataBindings.Add("Value", profile.ButtonsMacro, "HueIndex", false, DataSourceUpdateMode.OnPropertyChanged);
            nameTextBox.DataBindings.Add("Text", profile.ButtonsMacro, "Name", false, DataSourceUpdateMode.OnValidation);
            aliasTextBox.DataBindings.Add("Text", profile.ButtonsMacro, "Alias", false, DataSourceUpdateMode.OnPropertyChanged);
            keyNameTextBox.DataBindings.Add("Text", profile.ButtonsMacro, "KeyName", false, DataSourceUpdateMode.OnPropertyChanged);
            command1TextBox.DataBindings.Add("Text", profile.ButtonsMacro, "Command1", false, DataSourceUpdateMode.OnPropertyChanged);
            command2TextBox.DataBindings.Add("Text", profile.ButtonsMacro, "Command2", false, DataSourceUpdateMode.OnPropertyChanged);
            command3TextBox.DataBindings.Add("Text", profile.ButtonsMacro, "Command3", false, DataSourceUpdateMode.OnPropertyChanged);
            //hotkeyControl.DataBindings.Add("Value", profile.ButtonsMacro, "KeysCombo", false, DataSourceUpdateMode.OnPropertyChanged);
            descriptionTextBox.DataBindings.Add("Text", profile.ButtonsMacro, "Description", false, DataSourceUpdateMode.OnPropertyChanged);
            #endregion

            foreach (Control control in Icons.Values)
                control.Dispose();
            Icons.Clear();

            RefreshOnButtonMacroSelectedChanged();
            UpdateUndoRedoButtons(History, new HistoryEventArgs(History.TotalUndoCommands, History.TotalRedoCommands));

            _FormsHandle.Clear();
            _FormsHandle.Add(Handle);
            foreach (ButtonMacro macro in profile.ButtonsMacro)
                NewIconForm(macro);

            Text = SpellCasterManager.CurrentProfileName + " - SpellCaster3";
            notifyIcon.Text = Text;
        }

        private void SaveProfile(string path)
        {
            try
            {
                SpellCasterManager.Save(path);
            }
            catch (Exception saveException)
            {
                ErrorForm.Show("Salvataggio del file fallito", saveException);
                return;
            }

            SpellCasterManager.UndoSaveCommandsAmount = History.TotalUndoCommands;
            UpdateUndoRedoButtons(null, new HistoryEventArgs(History.TotalUndoCommands, History.TotalRedoCommands));

            System.Media.SystemSounds.Asterisk.Play();
            Text = SpellCasterManager.CurrentProfileName + " - SpellCaster3";
            notifyIcon.Text = Text;
        }

        private void ButtonsMacro_ListChanging(object sender, Expand.ComponentModel.ListChangingEventArgs e)
        {
            switch (e.ListChangedType)
            {
                case ListChangedType.ItemChanged:
                    if (iconsListBox.SelectedIndices.Count == 1 && !History.Undoing && !History.Redoing && e.PropertyDescriptor != null)
                    {
                        switch (e.PropertyDescriptor.Name)
                        {
                            case "GumpIndex":
                                History.Done(new Commands.SetButtonMacroPropertyCommand<int>(this, SpellCasterManager.CurrentProfile.ButtonsMacro[e.NewIndex], e.PropertyDescriptor.Name, gumpButton.Value));
                                break;
                            case "HueIndex":
                                History.Done(new Commands.SetButtonMacroPropertyCommand<int>(this, SpellCasterManager.CurrentProfile.ButtonsMacro[e.NewIndex], e.PropertyDescriptor.Name, hueButton.Value));
                                break;
                            case "KeyName":
                                History.Done(new Commands.SetButtonMacroPropertyCommand<string>(this, SpellCasterManager.CurrentProfile.ButtonsMacro[e.NewIndex], e.PropertyDescriptor.Name, keyNameTextBox.Text));
                                break;
                            case "KeysCombo":
                                History.Done(new Commands.SetButtonMacroPropertyCommand<Keys>(this, SpellCasterManager.CurrentProfile.ButtonsMacro[e.NewIndex], e.PropertyDescriptor.Name, hotkeyControl.Value));
                                break;
                            default:
                                string txtBox = e.PropertyDescriptor.Name.ToLower() + "TextBox";
                                System.Reflection.FieldInfo field = GetType().GetField(txtBox, System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
                                TextBox propertyTextbox = field.GetValue(this) as TextBox;
                                History.Done(new Commands.SetButtonMacroPropertyCommand<string>(this, SpellCasterManager.CurrentProfile.ButtonsMacro[e.NewIndex], e.PropertyDescriptor.Name, propertyTextbox.Text));
                                break;
                            case "Position":
                                break;
                        }
                    }
                    break;
                case ListChangedType.ItemDeleted:

                    break;
            }
        }

        private void ButtonsMacro_ListChanged(object sender, ListChangedEventArgs e)
        {
            if (SelectedButtonMacro != null)
                Icons[SelectedButtonMacro].SyncWithButtonMacro(SelectedButtonMacro);
        }

        private void UpdateUndoRedoButtons(object sender, HistoryEventArgs e)
        {
            undoToolStripButton.Enabled = History.CanBeUndone;
            annullaToolStripMenuItem.Enabled = undoToolStripButton.Enabled;

            redoToolStripButton.Enabled = History.CanBeRedone;
            ripristinaToolStripMenuItem.Enabled = redoToolStripButton.Enabled;

            salvaToolStripButton.Enabled = SpellCasterManager.UndoSaveCommandsAmount != History.TotalUndoCommands;
            salvaToolStripMenuItem.Enabled = salvaToolStripButton.Enabled;
        }

        private int? _SelectedButtonMacroIndex;
        public int? SelectedButtonMacroIndex
        {
            private set
            {
                _SelectedButtonMacroIndex = value;
                iconSettingsGroupBox.Enabled = _SelectedButtonMacroIndex.HasValue;
            }
            get
            {
                return _SelectedButtonMacroIndex;
            }
        }

        public ButtonMacro SelectedButtonMacro
        {
            get { return (SelectedButtonMacroIndex.HasValue ? iconsListBox.Items[SelectedButtonMacroIndex.Value] as ButtonMacro : null); }
            set
            {
                if (!SpellCasterManager.CurrentProfile.ButtonsMacro.Contains(value)) throw new InvalidOperationException("collection must contain buttonmacro");
                if (SelectedButtonMacro != value)
                {
                    iconsListBox.ClearSelected();
                    iconsListBox.SelectedIndex = SpellCasterManager.CurrentProfile.ButtonsMacro.IndexOf(value);
                    SelectedButtonMacroIndex = iconsListBox.SelectedIndex;
                }
            }
        }

        private void iconsListBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            _ChangingSelectedButtonMacro = true;
            RefreshOnButtonMacroSelectedChanged();
            _ChangingSelectedButtonMacro = false;
        }

        private void RefreshOnButtonMacroSelectedChanged()
        {
            deleteButton.Enabled = (iconsListBox.SelectedIndices.Count > 0);
            copiaToolStripButton.Enabled = (iconsListBox.SelectedIndices.Count > 0);
            copiaToolStripMenuItem.Enabled = (iconsListBox.SelectedIndices.Count > 0);
            SelectedButtonMacroIndex = (iconsListBox.SelectedIndices.Count == 1 ? (int?)iconsListBox.SelectedIndex : null);
            if (SelectedButtonMacro != null) hotkeyControl.Value = SelectedButtonMacro.KeysCombo;
            else hotkeyControl.Reset();
        }

        private void iconForms_MoveBegin(object sender, EventArgs e)
        {
        }

        private void iconForms_MoveEnd(object sender, MoveEventArgs e)
        {
            IconForm form = sender as IconForm;
            if (e.IsLocationChanged)
            {
                form.ButtonMacro.Position = form.Location;
                History.Done(new Commands.ChangeLocationButtonMacroCommand(this, form.ButtonMacro, e.LastLocation.Value, form.Location));
            }
        }

        internal void NewIconForm(ButtonMacro macro)
        {
            Icons.Add(macro, IconForm.FromButtonMacro(macro));
            _FormsHandle.Add(Icons[macro].Handle);
            Icons[macro].MoveBegin += IconFormMoveBeginHandler;
            Icons[macro].MoveEnd += IconFormMoveEndHandler;
            Icons[macro].Show();
        }

        internal void DeleteIconForm(ButtonMacro macro)
        {
            _FormsHandle.Remove(Icons[macro].Handle);
            Icons[macro].MoveBegin -= IconFormMoveBeginHandler;
            Icons[macro].MoveEnd -= IconFormMoveEndHandler;
            Icons[macro].Dispose();
            Icons.Remove(macro);
        }

        private void hueButton_ValueChanged(object sender, EventArgs e)
        {
            gumpButton.HueIndex = hueButton.Value;
        }

        private void aliasTextBox_TextChanged(object sender, EventArgs e)
        {
            gumpButton.Alias = aliasTextBox.Text;
        }

        private void keyNameTextBox_TextChanged(object sender, EventArgs e)
        {
            gumpButton.KeyName = keyNameTextBox.Text;
        }

        protected override void OnClientSizeChanged(EventArgs e)
        {
            if (WindowState != _LastWindowState)
            {
                OnWindowStateChanged(this, new LastValueEventArgs<FormWindowState>(_LastWindowState));
                _LastWindowState = WindowState;
            }
            base.OnClientSizeChanged(e);
        }

        public event EventHandler<LastValueEventArgs<FormWindowState>> WindowStateChanged;

        protected void OnWindowStateChanged(object sender, LastValueEventArgs<FormWindowState> e)
        {
            if (WindowStateChanged != null) WindowStateChanged(sender, e);
        }

        private void HomeForm_WindowStateChanged(object sender, LastValueEventArgs<FormWindowState> e)
        {
            if (WindowState == FormWindowState.Minimized)
            {
                Visible = false;
                notifyIcon.Visible = true;
                if (SpellCaster3.Properties.Settings.Default.ShowTrayIconMessage)
                {
                    notifyIcon.ShowBalloonTip(3000, "Informazione", "Applicazione ridotta a icona nella system tray", ToolTipIcon.Info);
                    SpellCaster3.Properties.Settings.Default.ShowTrayIconMessage = false;
                    SpellCaster3.Properties.Settings.Default.Save();
                }
                _BeforeHideWindowState = e.LastValue;
            }
            else
                notifyIcon.Visible = false;
        }

        public void Restore()
        {
            Visible = true;
            WindowState = _BeforeHideWindowState;
            ShowAllIcons();
        }

        private bool ApplicationFocused(IntPtr currentFocus)
        {
            bool res;
            try
            {
                if (InvokeRequired)
                    res = (bool)Invoke(new Func<HomeForm, IntPtr, bool>((HomeForm control, IntPtr search) => { return control._FormsHandle.Contains(search); }), this, currentFocus);
                else
                    res = _FormsHandle.Contains(currentFocus);
            }
            catch (Exception)
            {
                return false;
            }

            return res;
        }
        
        /*private void Windows_ForegroundWindowChanged(object sender, LastValueEventArgs<IntPtr> e)
        {
            if (WindowState == FormWindowState.Minimized)
            {
                if (ExEnvironment.Windows.ForegroundWindow.ToInt64() == Client.Handle.GetHandle().ToInt64() || ApplicationFocused(ExEnvironment.Windows.ForegroundWindow))
                    ShowAllIcons();
                else
                    HideAllIcons();
            }
            else
                ShowAllIcons();
        }*/

        private void HomeForm_Load(object sender, EventArgs e)
        {
            if (DesignMode) return;

            if (SpellCasterManager.LoadMinimized)
            {
                BeginInvoke(new MethodInvoker(delegate
                {
                    // Questa riga schifosa è usata per simulare un input alla finestra prima che venga minimizzata, in questo modo
                    // è possibile settare le icone come foreground fin dall'inizio, altrimenti verrebbero lanciate eccezioni
                    MouseKeyboardLibrary.KeyboardSimulator.KeyPress(Keys.V);
                    if (Control.ModifierKeys == Keys.Shift)
                        return;
                    else
                        WindowState = FormWindowState.Minimized;
                }));
            }
        }

        private void assignFromHotkeyButton_Click(object sender, EventArgs e)
        {
            keyNameTextBox.Text = SelectedButtonMacro.KeysComboReadableString;
            keyNameTextBox_TextChanged(sender, e);
        }

        private bool dragDropValidData = false;
        private void HomeForm_DragDrop(object sender, DragEventArgs e)
        {
            string filepath = null;
            if (dragDropValidData)
            {
                IDataObject data = e.Data;
                string[] stringArrayData = data.GetData("FileNameW") as string[];
                if (stringArrayData != null)
                {
                    filepath = stringArrayData[0];
                    if (Path.GetExtension(filepath) != ".spx") filepath = null;
                }

                if (ShouldSave)
                    if (MessageBox.Show("Il profilo è stato modificato, vuoi salvarlo?", "Attenzione", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) == DialogResult.Yes)
                        salvaToolStripMenuItem_Click(sender, e);

                try
                {
                    SpellCasterManager.Load(filepath);
                    LoadProfile(SpellCasterManager.CurrentProfile);
                }
                catch (FileNotFoundException)
                {
                    ErrorForm.Show("File non valido o non trovato");
                }
                catch (Exception loadException)
                {
                    ErrorForm.Show("Apertura del file fallita probabilmente per un formato errato", loadException);
                }
            }
        }

        private void HomeForm_DragEnter(object sender, DragEventArgs e)
        {
            dragDropValidData = false;
            e.Effect = DragDropEffects.None;
            IDataObject data = e.Data;
            string[] stringArrayData = data.GetData("FileNameW") as string[];
            if (stringArrayData != null)
            {
                string filepath = stringArrayData[0];
                if (Path.GetExtension(filepath) != ".spx") return;
                dragDropValidData = true;
                e.Effect = DragDropEffects.Link;
            }
        }

        private void openFileDialog_FileOk(object sender, CancelEventArgs e)
        {
            SpellCaster3.Properties.Settings.Default.LastLoadPath = Path.GetDirectoryName(openFileDialog.FileName);
            openFileDialog.InitialDirectory = SpellCaster3.Properties.Settings.Default.LastLoadPath;
            SpellCaster3.Properties.Settings.Default.Save();
        }

        private void saveFileDialog_FileOk(object sender, CancelEventArgs e)
        {
            SpellCaster3.Properties.Settings.Default.LastSavePath = Path.GetDirectoryName(saveFileDialog.FileName);
            saveFileDialog.InitialDirectory = SpellCaster3.Properties.Settings.Default.LastSavePath;
            SpellCaster3.Properties.Settings.Default.Save();
        }

        private void hotkeyControl_ValueChanged(object sender, EventArgs e)
        {
            if (SelectedButtonMacro == null || _ChangingSelectedButtonMacro) return;
            SelectedButtonMacro.KeysCombo = hotkeyControl.Value;
        }

        private void iconsVisibilityWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            while (!Disposing && !IsDisposed)
            {
                if (WindowState == FormWindowState.Minimized)
                {
                    if (ExEnvironment.Windows.ForegroundWindow.ToInt64() == Client.Handle.GetHandle().ToInt64() || ApplicationFocused(ExEnvironment.Windows.ForegroundWindow))
                        ShowAllIcons();
                    else
                        HideAllIcons();
                }
                else
                    ShowAllIcons();

                System.Threading.Thread.Sleep(1);
            }
        }
    }
}
